package com.demo;

import java.util.LinkedHashMap;
import java.util.Map;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;

public class BeanPostProcessor {
	// org.springframework.web.context.support.SpringBeanAutowiringSupport
	private static final String[] ANNOTATION_PROCESSOR_BEAN_NAMES = {
			// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
			AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME,
			// org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor
			AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME,
			// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor for JSR-250
			AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME,
			// org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor for JPA
			AnnotationConfigUtils.PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME };

	private final Map<String, InstantiationAwareBeanPostProcessor> ANNOTATION_PROCESSORS = new LinkedHashMap<String, InstantiationAwareBeanPostProcessor>(
			4);

	// ---------------------------------------------------------------------
	// Static API used as entrance points to the fluent API
	// ---------------------------------------------------------------------

	public static BeanPostProcessor on(ApplicationContext applicationContext) {
		return new BeanPostProcessor(applicationContext);
	}

	// ---------------------------------------------------------------------
	// Members
	// ---------------------------------------------------------------------
	private ApplicationContext applicationContext;

	// ---------------------------------------------------------------------
	// Constructors
	// ---------------------------------------------------------------------
	private BeanPostProcessor(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
		this.registerAnnotationConfig();
		this.doResolveInstantiationAwareBeanPostProcessor();
	}

	// ---------------------------------------------------------------------
	// Object API
	// ---------------------------------------------------------------------
	public ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	public void processInjection(Object bean) {
		for (InstantiationAwareBeanPostProcessor processor : ANNOTATION_PROCESSORS.values()) {
			if (processor != null) {
				try {
					processor.postProcessPropertyValues(null, null, bean, null);
				} catch (Exception e) {
					// ignore exception
				}
			}
		}
	}

	private void doResolveInstantiationAwareBeanPostProcessor() {
		for (String beanName : ANNOTATION_PROCESSOR_BEAN_NAMES) {
			if (applicationContext.containsBean(beanName)) { //
				ANNOTATION_PROCESSORS.put(beanName,
						applicationContext.getBean(beanName, InstantiationAwareBeanPostProcessor.class));
			}
		}
	}

	/**
	 * <annotation-config/>
	 */
	private void registerAnnotationConfig() {
		BeanDefinitionRegistry registry = unwrapBeanDefinitionRegistry(applicationContext);
		AnnotationConfigUtils.registerAnnotationConfigProcessors(registry);
	}

	private BeanDefinitionRegistry unwrapBeanDefinitionRegistry(ApplicationContext context) {
		if (context instanceof ConfigurableApplicationContext) {
			ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext) context).getBeanFactory();
			if (factory instanceof BeanDefinitionRegistry) { //
				return (BeanDefinitionRegistry) factory;
			}
			return null;
		} else {
			return null;
		}
	}
}
